﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Collections.Generic;

namespace SGCG.Model
{
    public class PellBitmap
    {
        private readonly WriteableBitmap bitmap;
        //private readonly WriteableBitmap emptyBitmap;
        private readonly int[] emptyPixels, renderPixels;
        private readonly IDictionary<int, IList<GraphicObject>> graphicObjects = new Dictionary<int, IList<GraphicObject>>();
        private readonly IDictionary<GraphicObject, int> zIndexes = new Dictionary<GraphicObject, int>();
        // TODO quando o preenchimento estiver implementado, acho que vamos querer repensar a estrutura de dados desse pixelOwners...
        private GraphicObject[] pixelOwners, emptyPixelOwners;
        private readonly IDrawingCanvas nullCanvas;

        public PellBitmap()
        {
            this.PendingBatchOperations = 0;
            this.bitmap = this.CreateBitmap();
            //this.emptyBitmap = this.CreateBitmap();
            this.emptyPixels = CreateEmptyPixels();
            this.renderPixels = CreateEmptyPixels();
            this.pixelOwners = CreateEmptyPixelsOwners();
            this.emptyPixelOwners = CreateEmptyPixelsOwners();
            this.nullCanvas = new DrawingCanvas(this, null);
            RequestRedraw();
        }

        public void AddObject(GraphicObject graphicObject, int ZIndex = 0)
        {
            if (!this.graphicObjects.ContainsKey(ZIndex))
            {
                this.graphicObjects[ZIndex] = new List<GraphicObject>();
            }

            graphicObject.Attach(new DrawingCanvas(this, graphicObject));
            this.graphicObjects[ZIndex].Add(graphicObject);
            this.zIndexes[graphicObject] = ZIndex;

            RequestRedraw();
        }

        public void RemoveObject(GraphicObject graphicObject)
        {
            int zIndex = this.zIndexes[graphicObject];
            this.zIndexes.Remove(graphicObject);
            this.graphicObjects[zIndex].Remove(graphicObject);
            graphicObject.Deleted = true;

            RequestRedraw();
        }

        public Group GroupObjects(IEnumerable<GraphicObject> graphicObjects)
        {
            if (!graphicObjects.Any()) return null;

            int MaxZIndex = graphicObjects.Max(graphicObject => zIndexes[graphicObject]);
            graphicObjects.Each(graphicObject => this.RemoveObject(graphicObject));
            
            Group group;
            this.AddObject(group = new Group(null, graphicObjects.ToArray()), MaxZIndex);
            return group;
        }

        public IEnumerable<GraphicObject> UngroupObjects(Group group)
        {
            int zIndex = this.zIndexes[group];
            this.RemoveObject(group);
            group.Each(graphicObject => this.AddObject(graphicObject, zIndex));

            return group;
        }

        private int PendingBatchOperations { get; set; }

        internal void PushBatchOperation()
        {
            PendingBatchOperations++;
        }

        internal void PopBatchOperation()
        {
            PendingBatchOperations--;
        }

        public bool IsInBatchUpdateMode 
        {
            get
            {
                return PendingBatchOperations > 0;
            }
        }

        public void RequestRedraw()
        {
            if (this.IsInBatchUpdateMode)
            {
                return;
            }

            // Logging draw
            UserMessage.LogDraw();

            try
            {
                // we cannot use BatchRedrawOperation for, well... obvious reasons. Just look at the code.
                PushBatchOperation();

                //this.pixelOwners = new GraphicObject[this.bitmap.Pixels.Length];
                emptyPixelOwners.CopyTo(pixelOwners, 0);

                ClearBitmap();

                graphicObjects.OrderBy(kvp => kvp.Key)
                              .SelectMany(kvp => kvp.Value)
                              .Each(graphicObject => graphicObject.Draw());

                renderPixels.CopyTo(bitmap.Pixels, 0);

                bitmap.Invalidate();
            }
            finally
            {
                PopBatchOperation();
            }
        }

        private class ComparerByDrawingOrder : IComparer<GraphicObject>
        {
            public ComparerByDrawingOrder(IDictionary<int, IList<GraphicObject>> graphicObjects)
            {
                this.graphicObjects =
                    graphicObjects
                    .OrderBy(kvp => kvp.Key)
                    .SelectMany(kvp => kvp.Value)
                    .ToArray();
            }

            public int Compare(GraphicObject x, GraphicObject y)
            {
                return Array.IndexOf(graphicObjects, x).CompareTo(Array.IndexOf(graphicObjects, y));
            }

            private GraphicObject[] graphicObjects;
        }

        public IComparer<GraphicObject> CreateComparerByDrawingOrder()
        {
            return new ComparerByDrawingOrder(graphicObjects);
        }

        protected virtual WriteableBitmap CreateBitmap()
        {
            WriteableBitmap bitmap = new WriteableBitmap(600, 600);
            return bitmap;
        }

        protected virtual int[] CreateEmptyPixels()
        {
            int[] pixels = new int[600 * 600];
            for (int i = 0; i < 600 * 600; ++i)
                pixels[i] = -1;
            return pixels;
        }

        protected virtual GraphicObject[] CreateEmptyPixelsOwners()
        {
            GraphicObject[] v = new GraphicObject[600 * 600];
            return v;
        }

        protected virtual void ClearBitmap()
        {
            //emptyBitmap.Pixels.CopyTo(bitmap.Pixels, 0);
            emptyPixels.CopyTo(renderPixels, 0);
        }

        public WriteableBitmap Bitmap
        {
            get { return this.bitmap; }
        }

        private class DrawingCanvas : IDrawingCanvas
        {
            private readonly PellBitmap owner;
            private readonly GraphicObject currentObject;

            public DrawingCanvas(PellBitmap owner, GraphicObject currentObject)
            {
                this.owner = owner;
                this.currentObject = currentObject;
            }

            void IDrawingCanvas.SetPixel(int x, int y, byte alpha, byte r, byte g, byte b)
            {
                if (x >= 0 && y >= 0 && x < owner.bitmap.PixelWidth && y < owner.bitmap.PixelHeight)
                {
                    int i = y * owner.bitmap.PixelWidth + x;
                    //owner.bitmap.Pixels[i] = (alpha << 24) | (r << 16) | (g << 8) | b;
                    owner.renderPixels[i] = (alpha << 24) | (r << 16) | (g << 8) | b;

                    if (this.currentObject != null)
                    {
                        owner.pixelOwners[i] = currentObject;
                    }
                }
            }

            void IDrawingCanvas.SetPixel(int x, int y, Color color)
            {
                ((IDrawingCanvas)this).SetPixel(x, y, color.A, color.R, color.G, color.B);
            }

            void IDrawingCanvas.SendRedrawRequest()
            {
                this.owner.RequestRedraw();
            }

            public int GetWidth()
            {
                return owner.bitmap.PixelWidth;
            }

            public int GetHeight()
            {
                return owner.bitmap.PixelHeight;
            }
        }

        public GraphicObject GetObjectAtCoordinates(int x, int y)
        {
            if (x < 0 || y < 0 || x >= bitmap.PixelWidth || y >= bitmap.PixelHeight)
                return null;

            int i = y * bitmap.PixelWidth + x;

            return this.pixelOwners[i];
        }        
    }
}
